﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.IO;

namespace OrganizationName.Runtime
{
    public class Loader
    {
        private const string ASSEMBLY_FILE_EXTENSION = ".dll";

        private static Loader _Instance = new Loader();

        public static Loader Instance
        {
            get
            {
                return _Instance;
            }
        }

        private SortedList<string, LoaderOutput> _Cache = new SortedList<string, LoaderOutput>();
        private AppDomain _CurrentDomain = AppDomain.CurrentDomain;

        protected string GetCacheKey(string assemblyName)
        {
            return GetCacheKey(new AssemblyName(assemblyName));
        }

        protected string GetCacheKey(AssemblyName assemblyName)
        {
            return assemblyName.Name.ToLowerInvariant();
        }

        public virtual AssemblyLoadPath[] AssemblyAlternativePaths
        {
            get
            {
                return new AssemblyLoadPath[]
                {
                    new AssemblyLoadPath() { Path = @"C:\Projects\Akbank.InternetBanking.2010\Main\Source\Presentation\WebApplication.UI\Bin" }
                };
            }
        }

        public LoaderOutput LoadType(string typeName)
        {
            return new LoaderOutput() { Type = Type.GetType(typeName, AssemblyResolver, null, true) };
        }

        private Assembly AssemblyResolver(AssemblyName assemblyName)
        {
            return Load(assemblyName).Assembly;
        }

        public virtual LoaderOutput Load(string assemblyName)
        {
            return Load(new AssemblyName(assemblyName));
        }

        public virtual LoaderOutput Load(Assembly assembly)
        {
            return Load(assembly.GetName());
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public virtual LoaderOutput Load(AssemblyName assemblyName)
        {
            string assemblyNameKey = GetCacheKey(assemblyName);

            if (!_Cache.ContainsKey(assemblyNameKey))
            {
                LoaderOutput loaderOutput = new LoaderOutput();

                Assembly loadedAssembly = LoadAssemblyFromAssemblyName(assemblyName);

                loaderOutput.Assembly = loadedAssembly;

                _Cache.Add(assemblyNameKey, loaderOutput);

                foreach (AssemblyName referencedAssembly in loadedAssembly.GetReferencedAssemblies())
                {
                    LoaderOutput referencedAssemblyLoaderOutput = Load(referencedAssembly);

                    if (!loaderOutput.References.Contains(referencedAssemblyLoaderOutput.Assembly))
                    {
                        loaderOutput.References.Add(referencedAssemblyLoaderOutput.Assembly);
                    }

                    foreach (Assembly resolvedReferencedAssembly in referencedAssemblyLoaderOutput.References)
                    {
                        if (!loaderOutput.References.Contains(resolvedReferencedAssembly))
                        {
                            loaderOutput.References.Add(resolvedReferencedAssembly);
                        }
                    }
                }
            }

            return _Cache[assemblyNameKey];
        }

        private Assembly LoadAssemblyFromAssemblyName(AssemblyName assemblyName)
        {
            Assembly loadedAssembly = null;

            if (assemblyName.CodeBase != null)
            {
                Assembly assembly = Assembly.LoadFrom(assemblyName.CodeBase);
                loadedAssembly = assembly;
            }
            else
            {
                try
                {
                    loadedAssembly = _CurrentDomain.Load(assemblyName);
                }
                catch (FileNotFoundException)
                {
                    if (AssemblyAlternativePaths != null)
                    {
                        foreach (AssemblyLoadPath assemblyLoadPath in AssemblyAlternativePaths)
                        {
                            if (!string.IsNullOrEmpty(assemblyLoadPath.Path))
                            {
                                if (Uri.IsWellFormedUriString(assemblyLoadPath.Path, UriKind.Absolute))
                                {
                                    // TODO: Get assembly from web service
                                }
                                else
                                {
                                    string fullPathOfAssembly = Path.Combine(assemblyLoadPath.Path, assemblyName.Name + ASSEMBLY_FILE_EXTENSION);
                                    if (File.Exists(fullPathOfAssembly))
                                    {
                                        loadedAssembly = Assembly.LoadFrom(fullPathOfAssembly);
                                    }
                                }
                            }

                            if (loadedAssembly != null)
                            {
                                break;
                            }
                        }
                    }
                }
            }

            if (loadedAssembly == null)
            {
                throw new FileNotFoundException("The assembly " + assemblyName.Name + ".dll could not be found in any of the AssemblyLoadPath specified");
            }

            return loadedAssembly;
        }

        //internal Assembly GetAssemblyFromWebService(string assemblyName, bool addReferenceIfFound, string webServiceUrl)
        //{
        //    return WebServiceCall(assemblyName, webServiceUrl);
        //}

        //internal Assembly WebServiceCall(string assemblyName, string webServiceUrl)
        //{
        //    Assembly loadingAssembly = null;

        //    AssemblyServiceStub service = new AssemblyServiceStub();
        //    service.Url = webServiceUrl;

        //    AssemblyServiceResponse webServiceResponse = service.GetAssembly(assemblyName);
        //    if (string.IsNullOrEmpty(webServiceResponse.ErrorMessage))
        //    {
        //        string downloadedAssemblyPath = Path.Combine(_TemporaryDirectory.FullName, assemblyName + ASSEMBLY_FILE_EXTENSION);
        //        File.WriteAllBytes(downloadedAssemblyPath, webServiceResponse.AssemblyRawData);
        //        loadingAssembly = Assembly.LoadFrom(downloadedAssemblyPath);
        //    }
        //    else
        //    {
        //        return null;
        //    }

        //    return loadingAssembly;
        //}
    }
}
